---
title: Caching Actions
description: You can cache actions in Stagehand to avoid redundant LLM calls.
---

Caching actions in Stagehand is useful for actions that are expensive to run, or when the underlying DOM structure is not expected to change.

## Using `observe` to preview an action
`observe` lets you preview an action before taking it. If you are satisfied with the action preview, you can run it in `page.act` with no further LLM calls.

<CodeGroup>
```typescript TypeScript
const [actionPreview] = await page.observe("Click the quickstart link");

/** actionPreview is a JSON-ified version of a Playwright action:
{
	description: "The quickstart link",
	method: "click",
	selector: "/html/body/div[1]/div[1]/a",
	arguments: [],
}
**/

// NO LLM INFERENCE when calling act on the preview
await page.act(actionPreview)
```

```python Python
actions = await page.observe("Click the quickstart link")
action_preview = actions[0]

# action_preview is a dictionary version of a Playwright action:
# {
#	"description": "The quickstart link",
#	"method": "click",
#	"selector": "/html/body/div[1]/div[1]/a",
#	"arguments": [],
# }

# NO LLM INFERENCE when calling act on the preview
await page.act(action_preview)
```
</CodeGroup>

## Simple caching

Let's use a simple file-based cache for this example. We'll write a getter and a setter functions that can read and write to a JSON file:

<CodeGroup>
```typescript TypeScript
// Get the cached value (undefined if it doesn't exist)
async function getCache(key: string): Promise<ObserveResult | undefined> {
  try {
    const cache = await readFile("cache.json");
    const parsed = JSON.parse(cache);
    return parsed[key];
  } catch {
    return undefined;
  }
}

// Set the cache value
async function setCache(key: string, value: ObserveResult): Promise<void> {
  const cache = await readFile("cache.json");
  const parsed = JSON.parse(cache);
  parsed[key] = value;
  await writeFile("cache.json", JSON.stringify(parsed));
}
```

```python Python
# Get the cached value (None if it doesn't exist)
async def get_cache(key: str) -> Optional[Dict[str, Any]]:
    try:
        async with aiofiles.open("cache.json", 'r') as f:
            cache_content = await f.read()
            parsed = json.loads(cache_content)
            return parsed.get(key)
    except (FileNotFoundError, json.JSONDecodeError):
        return None

# Set the cache value
async def set_cache(key: str, value: Dict[str, Any]) -> None:
    try:
        async with aiofiles.open("cache.json", 'r') as f:
            cache_content = await f.read()
            parsed = json.loads(cache_content)
    except (FileNotFoundError, json.JSONDecodeError):
        parsed = {}
    
    parsed[key] = value
    
    async with aiofiles.open("cache.json", 'w') as f:
        await f.write(json.dumps(parsed))
```
</CodeGroup>

### Act with cache
Let's write a function that will check the cache, get the action, and run it. If the action fails, we'll attempt to "self-heal", i.e. retry it with `page.act` directly.

<CodeGroup>
```typescript TypeScript
// Check the cache, get the action, and run it
// If selfHeal is true, we'll attempt to self-heal if the action fails
async function actWithCache(page: Page, key: string, prompt: string, selfHeal = false) {
	try {
		const cacheExists = await getCache(key);

		let action: ObserveResult;
		if (cacheExists) {
		// Get the cached action
		action = await getCache(prompt);
		} else {
		// Get the observe result (the action)
		[action] = await page.observe(prompt);

		// Cache the action
		await setCache(prompt, action);
		}

		// Run the action (no LLM inference)
		await page.act(action);
	} catch (e) {
		console.error(e);
		// in selfHeal mode, we'll retry the action
		if (selfHeal) {
			console.log("Attempting to self-heal...");
			await page.act(prompt);
		}
		else {
			throw e;
		}
	}
}
```

```python Python
# Check the cache, get the action, and run it
# If self_heal is true, we'll attempt to self-heal if the action fails
async def act_with_cache(page, key: str, prompt: str, self_heal: bool = False):
    try:
        cache_exists = await get_cache(key)

        if cache_exists:
            # Get the cached action
            action = await get_cache(prompt)
        else:
            # Get the observe result (the action)
            actions = await page.observe(prompt)
            action = actions[0]

            # Cache the action
            await set_cache(prompt, action)

        # Run the action (no LLM inference)
        await page.act(action)
    except Exception as e:
        print(f"Error: {e}")
        # in self_heal mode, we'll retry the action
        if self_heal:
            print("Attempting to self-heal...")
            await page.act(prompt)
        else:
            raise e
```
</CodeGroup>

You can now use `actWithCache` to run an action with caching:

<CodeGroup>
```typescript TypeScript
const prompt = "Click the quickstart link";
const key = prompt; // Simple cache key
// Attempt cached action or self-heal
await actWithCache(page, key, prompt);
```

```python Python
prompt = "Click the quickstart link"
key = prompt  # Simple cache key
# Attempt cached action or self-heal
await act_with_cache(page, key, prompt)
```
</CodeGroup>

## Advanced caching

The above example is simple, but you may want to cache actions based on the page contents. Also, if you have duplicate prompts, you should use a more unique key.

We want to leave caching logic up to you, but give you all the tools you need to implement your own caching strategy.

You can directly access the DOM and accessibility tree from Playwright's page object. Here's an example of how to access the page content:

<CodeGroup>
```typescript TypeScript
// Get the page content
const pageContent = await page.content();
```

```python Python
# Get the page content
page_content = await page.content()
```
</CodeGroup>

You may also want to use the accessibility tree, the DOM, or any other information to create a more unique key. You can do this as you please, with very similar logic to the above example.